/*
 * Package il.ac.biu.cs.grossmm.test
 * File Test1.java
 * Created on Jul 20, 2006
 *
 */
package il.ac.biu.cs.grossmm.tests.timerEvens;

import il.ac.biu.cs.grossmm.api.OperationFailedException;
import il.ac.biu.cs.grossmm.api.data.*;
import il.ac.biu.cs.grossmm.api.flow.*;
import il.ac.biu.cs.grossmm.api.keys.ArrayKey;
import il.ac.biu.cs.grossmm.api.keys.ArrayKeyPattern;
import il.ac.biu.cs.grossmm.api.keys.Attribute;
import il.ac.biu.cs.grossmm.api.keys.Key;
import il.ac.biu.cs.grossmm.api.server.Component;
import il.ac.biu.cs.grossmm.api.server.ComponentManager;
import il.ac.biu.cs.grossmm.api.server.Loader;
import il.ac.biu.cs.grossmm.api.server.ServiceManager;
import il.ac.biu.cs.grossmm.api.server.Wrapped;
import il.ac.biu.cs.grossmm.api.server.Wrapper;
import il.ac.biu.cs.grossmm.impl.activeData.ActiveDataManagerImpl;
import il.ac.biu.cs.grossmm.impl.server.BootStrap;
import il.ac.biu.cs.grossmm.impl.server.Registry;
import il.ac.biu.cs.grossmm.api.server.Service;

import java.util.HashSet;
import java.util.Set;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

import static il.ac.biu.cs.grossmm.api.data.NodeTypeByInterface.*;

public class Test1 {
    /**
     * Logger for this class
     */
    private static final Logger logger = Logger.getLogger(Test1.class);

    public static void main(String[] args) throws Exception {
        BasicConfigurator.configure();
        
        Property p = TimerNode.TIME;
        
        BootStrap server = new BootStrap(new ServerLoader(), ServerLoader.class);
        
        server.start(null);
        
        Thread.sleep(15000);
        
        server.stop(null);
    }
}



enum Attributes implements Attribute {
    TIME;
}

class ServerLoader implements Loader, Service {
    /**
     * Logger for this class
     */
    private static final Logger logger = Logger.getLogger(ServerLoader.class);
    Registry registry = new Registry();
    
    ServerLoader() {
        registry.register(ActiveDataManager.class, new ActiveDataManagerImpl());
        registry.register("1", new TimerNotifier());
        registry.register("2", new TimerSubscriber());
        /*
        registry.putObject(PersistentPointManager.class, new PersistentPointManagerImpl());
        registry.putObject(SqlDataSource.class, 
                new DataSourceAdapter("presence_db", "sa", ""));
                */
    }
    
    public Wrapped load(Wrapper wrapper, Object hint) throws Exception {
        Wrapped w = wrapper.wrap(this);
        w.setLoader(registry);
        return w;
    }

    public void start(ServiceManager serviceManager) throws Exception {
        if (logger.isDebugEnabled()) {
            logger.debug("start() - start");
        }

        serviceManager.requireService("1");
        serviceManager.requireService("2");

        if (logger.isDebugEnabled()) {
            logger.debug("start(ServiceManager) - end");
        }
    }

    public void stop(Exception exception) {
        logger.info("Server stopped");
    }

    public void terminated(Object serviceHint, Exception e) {
        // TODO Implement terminated
        // 
        throw new RuntimeException("Not implemented");
    }
}

class TimerNotifier extends Thread implements Notifier, Component, Service {
    /**
     * Logger for this class
     */
    private static final Logger logger = Logger.getLogger(TimerNotifier.class);
    NotificationPoint<TimerNode> np;

    Set<Key> keys = new HashSet<Key>();
    
    TimerNotifier() {
    }
    
    @Override
    public void run() {
        int tick = 0;
        try {
            while( !interrupted() ) {
                logger.info("Tick: " + tick);
                
                for( Key key : keys ) {
                    int i = (Integer) key.subkey(Attributes.TIME).value();
                    if( tick % i == 0 ) {
                        logger.info("Notifying " + i);
                        Node<TimerNode> node = np.getRoot(key);
                        NodeManipulator man = np.writeLock(node);
                        try {
							man.setValue(node, TimerNode.TIME, tick);
						} catch (OperationFailedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
                        np.writeUnlock(node);
                    }
                }
                
                sleep(1000);
                
                tick++;
            }
        } catch( InterruptedException ie ) {}
    }

    public boolean subscribe(Key key, Track track) {
        assert( ! keys.contains(key) );
        
        int i = (Integer) key.subkey(Attributes.TIME).value();
        logger.info("Subscribed "+i);
        
        Root<TimerNode> node;
		try {
			node = np.activated(key);
		} catch (NoSuchSubscription e1) {
			e1.printStackTrace();
			return false;
		}
        NodeManipulator man = np.writeLock(node);
        try {
			man.setValue(node, TimerNode.TIME, 0);
		} catch (OperationFailedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        np.writeUnlock(node);
                
        keys.add(key);
        return true;
    }

    public void unsubscribe(Key key) {
        assert( keys.contains(key) );
        
        int i = (Integer) key.subkey(Attributes.TIME).value();
        logger.info("Unsubscribed "+i);
        
        keys.remove(key);
    }

    public void aborted(Object hint, Exception e) {
        // TODO Implement aborted
        // 
        throw new RuntimeException("Not implemented");
    }

    public void setComponentManager(ComponentManager container) throws Exception {
        if (logger.isDebugEnabled()) {
            logger.debug("setComponentManager() - start");
        }

        ActiveDataManager adm = 
            (ActiveDataManager) container.getComponent(ActiveDataManager.class);
        
        ArrayKeyPattern pattern = new ArrayKeyPattern(false, null);
        pattern.add(Attributes.TIME, new ArrayKeyPattern(false, Integer.class), true);
        
        np = adm.createNotificationPoint(this, pattern, nodeType(TimerNode.class), 0);
        
        container.releaseComponent(ActiveDataManager.class);

        if (logger.isDebugEnabled()) {
            logger.debug("setComponentManager(ComponentManager) - end");
        }
    }

    public void start(ServiceManager serviceManager) throws Exception {
        logger.info("started");
        start();
    }

    public void stop(Exception exception) {
        logger.info("stopped");
        interrupt();
    }

    public void terminated(Object serviceHint, Exception e) {
        // TODO Implement terminated
        // 
        throw new RuntimeException("Not implemented");
    }

    public boolean fetch(Key key, Node node) {
        // TODO Implement fetch
        // return false;
        throw new RuntimeException("Not implemented");
    }

    public void fetch(Key key, Track track, Root node, NodeManipulator manipulator) {
        // TODO Implement fetch
        // 
        throw new RuntimeException("Not implemented");
    }    
}

class TimerSubscriber implements Subscriber<TimerNode>, Component, Service {
    /**
     * Logger for this class
     */
    private static final Logger logger = Logger.getLogger(TimerSubscriber.class);
    
    SubscriptionPoint<TimerNode> sp;

    public void activated(Key key, Root node) {
        logger.info("activated");
    }

    public void deactivated(Key key, Status status) {
        logger.info("deactivated");
    }

    public void notify(Root<TimerNode> node, NodeEventInspector inspector) {
        logger.info("notified");
        
		try {
			int val = inspector.getValue(node, TimerNode.TIME);
			int oldVal = inspector.getOldValue(node, TimerNode.TIME);
	        
	        logger.info("Old value: "+oldVal + " New value: "+val);
		} catch (OperationFailedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        
    }

    public void aborted(Object hint, Exception e) {
        // TODO Implement aborted
        // 
        throw new RuntimeException("Not implemented");
    }

    public void setComponentManager(ComponentManager container) throws Exception {
        if (logger.isDebugEnabled()) {
            logger.debug("setComponentManager() - start");
        }
        
        ActiveDataManager adm = 
            (ActiveDataManager) container.getComponent(ActiveDataManager.class);
        
        ArrayKeyPattern pattern = new ArrayKeyPattern(false, null);
        pattern.add(Attributes.TIME, new ArrayKeyPattern(false, Integer.class), true);
        
        sp = adm.createSubscriptionPoint(this, pattern, nodeType(TimerNode.class));

        if (logger.isDebugEnabled()) {
            logger.debug("setComponentManager(ComponentManager) - end");
        }
    }


    public void start(ServiceManager serviceManager) throws Exception {
        logger.info("started");
        
        ArrayKey key = new ArrayKey();
        key.addValue(Attributes.TIME, 2);
        
        sp.subscribe(key, null);
        
        key = new ArrayKey();
        key.addValue(Attributes.TIME, 3);
        
        sp.subscribe(key, null);
    }

    public void stop(Exception exception) {
        logger.info("stopped");
    }

    public void terminated(Object serviceHint, Exception e) {
        // TODO Implement terminated
        // 
        throw new RuntimeException("Not implemented");
    }
    
}